A compiler is a computer program that converts a set of source code into a set of object code or a set of machine code to create an executable program. Machine code is a set of instructions or data directly executable by a processor. A tracing just-in-time GIT) compiler creates a copy, herein referred to as a “trace”, of the executing code in an intermediate language for the purpose of optimizing code for execution. An intermediate language is a language readable by an abstract machine for the purpose of analyzing, interpreting, or further converting or translating a software program. JIT compilation converts a set of code at runtime into code readable by a processor. A tracing JIT compiler may improve the machine code at runtime, optimizing the trace for actual execution scenarios. Such optimizations may include removing redundant actions, combining actions that may be performed simultaneously, and other improvements.
A tracing JIT compiler may monitor the execution of intermediate language code at the instruction level in order to collect a precise trace, optimize the trace, and create machine code for the optimized trace. The machine code for the optimized trace may be linked into the regular code, so that the optimized trace replaces a portion of the regular code, referred to herein as an “operation segment”. Such tracing JIT compilers may be added to an interpreter to realize the execution of intermediate language code. Alternatively, the tracing JIT compilers may realize the intermediate language code execution by translation to machine code, instead of using an interpreter.
One embodiment of an execution environment may provide a program stack, with the invocation of a method creating a new stack frame. A stack is a last in, first out (LIFO) data structure. A stack frame is a machine dependent data structure containing subroutine state information. A recorded trace may be typically represented in static single assignment (SSA) form. A SSA form is an intermediate representation that assigns each variable once, with existing variables split into different versions for different values. In a SSA form, each instruction may operate on local variables and produce a value which is stored in a new unique local variable. Some of these local variables may have been initialized before the trace started. When the trace finishes, some of the derived values may have to be stored in particular local variables in the stack frames of the methods invoked along the trace.
The execution environment may have instructions to perform indirect memory accesses, such as reads or writes. An indirect memory accesses may associate a local variable with a pointer to a memory address on a stack frame on the stack. A pointer is a reference to a memory location containing a memory object. The memory address may also point to the heap, where static variables and objects are typically allocated in a language with a managed, or garbage-collected, memory, such as C++, Java®, Javascript®, or others. As the local variables are associated with a pointer to a memory address and not a value, the value stored at the memory address may be mutated by accesses to the memory location by other program threads. Such mutations of the values stored in local variables may be undetectable by a naïvely recorded trace containing just executed instructions. In some execution environments, pointers may be monitored and adjusted by the garbage collector, causing precise information about managed pointers to be beneficial to the garbage collector at all program locations, such as program locations residing in newly generated optimized trace code.